/*
 * Copyright Radosław Smogura, 2012. All rights reserved.
 *
 * The content of this file is confidential and it is intelectual property
 * of copyright owners.
 * 
 *
 * $URL: http://reprap-ide.googlecode.com/svn/trunk/RepRapIDE/RepRapProject/src/main/java/eu/softper/reprap/project/filetypes/StlMimeVisualElement.java $
 * $Id: StlMimeVisualElement.java 3 2012-11-19 10:58:30Z mail@smogura.eu $
 * $Revision: 3 $
 */
package eu.softper.reprap.project.filetypes;

import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.ColorCube;
import com.sun.j3d.utils.universe.PlatformGeometry;
import com.sun.j3d.utils.universe.SimpleUniverse;
import eu.smogura.stlparser.STLParser;
import eu.smogura.stlparser.STLParserFactory;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.io.InputStream;
import javax.media.j3d.*;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.vecmath.*;
import org.netbeans.core.spi.multiview.CloseOperationState;
import org.netbeans.core.spi.multiview.MultiViewElement;
import org.netbeans.core.spi.multiview.MultiViewElementCallback;
import org.openide.awt.UndoRedo;
import org.openide.filesystems.FileObject;
import org.openide.util.Lookup;
import org.openide.windows.TopComponent;

@MultiViewElement.Registration(displayName = "#LBL_StlMime_VISUAL",
iconBase = "eu/softper/reprap/project/images/drawing.png",
mimeType = "application/sla",
persistenceType = TopComponent.PERSISTENCE_NEVER,
preferredID = "StlMimeVisual",
position = 2000)
public final class StlMimeVisualElement extends JPanel implements MultiViewElement {

    private StlMimeDataObject obj;
    private JToolBar toolbar = new JToolBar();
    private transient MultiViewElementCallback callback;
    private Canvas3D canvas3d;
    private Canvas3DMouseAdapter mouseAdapter;
    
    private SimpleUniverse su;
    
    /** Main group used to display preview. The order of elements is as follow
     * <ol>
     *  <li>Printer outline, heating bad, surface.</li>
     *  <li>Lights</li>
     * </ol>
     */
    private BranchGroup mainGroup;
    
    private OrderedGroup lights;
    
    /** Group containing 3D models to display. */
    private BranchGroup objects;

    public StlMimeVisualElement(Lookup lkp) throws Exception {
        obj = lkp.lookup(StlMimeDataObject.class);
        assert obj != null;
        initComponents();
        inititialize3D();
        
        FileObject fo = obj.getPrimaryFile();
        objects.addChild(createObject3D(fo.getInputStream()));
    }

    protected void inititialize3D() {
        GraphicsConfigTemplate3D config = new GraphicsConfigTemplate3D();
        config.setSceneAntialiasing(GraphicsConfigTemplate3D.PREFERRED);
        config.setDoubleBuffer(GraphicsConfigTemplate3D.PREFERRED);
        config.setDepthSize(24);
        
        GraphicsConfiguration conf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getBestConfiguration(config);
        canvas3d = new Canvas3D(conf);
        canvas3d.addMouseWheelListener(mouseAdapter);
        
        add(canvas3d, BorderLayout.CENTER);
        
        /* Creates basic universe and basic groups. */
        su = new SimpleUniverse(canvas3d);
        mainGroup = new BranchGroup();
        mainGroup.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
        mainGroup.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
        mainGroup.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
        mainGroup.setCapability(BranchGroup.ALLOW_DETACH);
        
        /* Add surface (printer frame). */
        mainGroup.addChild(createSurface());

        /* Create lights. */
        lights = new OrderedGroup();
        lights.setCapability(OrderedGroup.ALLOW_CHILD_INDEX_ORDER_READ);
        lights.setCapability(OrderedGroup.ALLOW_CHILD_INDEX_ORDER_WRITE);
        lights.setCapability(OrderedGroup.ALLOW_CHILDREN_EXTEND);
        lights.setCapability(OrderedGroup.ALLOW_CHILDREN_READ);
        lights.setCapability(OrderedGroup.ALLOW_CHILDREN_WRITE);
        
        PointLight light = new PointLight(new Color3f(1.0f, 1.0f, 1.0f), new Point3f(0.0f, 00.0f, 5.0f) , new Point3f(1.0f, 0.01f, 0.0f));
        light.setInfluencingBounds(new BoundingSphere(new Point3d(0, 0, 0), 100.0));
        lights.setBoundsAutoCompute(true);
        lights.addChild(light);
        //mainGroup.addChild(lights);
        
        /* Objects group. */
        objects = new BranchGroup();
        objects.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
        objects.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
        objects.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
        objects.setCapability(BranchGroup.ALLOW_DETACH);
        
        mainGroup.addChild(objects);
        
        mainGroup.compile();
        
        /* Setup view parametes. */        
        su.getViewingPlatform().setNominalViewingTransform();
        View v = su.getViewer().getView();
        v.setBackClipDistance(200.0);
        
        {
            ColorCube avatar = new ColorCube(0.5d);
            Transform3D t3 = new Transform3D();
            t3.setTranslation(new Vector3f(0.0f, 0.0f, 0.0f));
            TransformGroup tg = new TransformGroup(t3);
            tg.addChild(lights);
            //ViewerAvatar vAvatar = new ViewerAvatar();
            //tg.addChild(avatar);
           // vAvatar.addChild(tg);
            //vAvatar.compile();
            PlatformGeometry pg = new PlatformGeometry();
            pg.addChild(tg);
            su.getViewingPlatform().setPlatformGeometry(pg);
        }
        
        /* Set orbit behaviour for roatating and moving view. */
        OrbitBehavior orbit = new OrbitBehavior(canvas3d);
        orbit.setSchedulingBounds(new BoundingSphere(new Point3d(0, 0, 0), 100.0));
        su.getViewingPlatform().setViewPlatformBehavior(orbit);
        
        /* and add main group to universe. */
        su.addBranchGraph(mainGroup);
    }
    
    /** Creates outline of pritner. We assume that bad (heating bad or printing
     * surface will not be greater then 0.0, due to proper showing of 3D models.
     * This method may generate some 3D objects above this for example frames
     * and others.
     * 
     * @return node describing printer bad and outline.
     */
    protected Node createSurface() {
        final float boxWidth = 1.0f;
        final float boxPadding = 0.2f;
        
        /* Specify appearance of heating bad. */
        Material m = new Material();
        m.setAmbientColor(new Color3f(0.4f, 0.4f, 0.4f));
        m.setDiffuseColor(new Color3f(0.5f, 0.5f, 0.5f));
        m.setSpecularColor(new Color3f(0.75f, 0.75f, 0.75f));
        m.setShininess(10.0f);
        Appearance heatingAppearance = new Appearance();
        heatingAppearance.setMaterial(m);
        
        /* Create shared box representing bad. */
        
        
        BranchGroup heatingSurfaceGrp = new BranchGroup();
        heatingSurfaceGrp.setCapability(BranchGroup.ALLOW_DETACH);
        heatingSurfaceGrp.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
        heatingSurfaceGrp.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
        
        /* Creates array of boxes. */
        for (int x=0; x < 20; x++) {
            for (int y=0; y < 20; y++) {
                /* Create required transform group and add bad. */
                TransformGroup heatingTransform = new TransformGroup();
                heatingTransform.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
                heatingTransform.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
                
                Transform3D heating3D = new Transform3D();

                /* Surface has height of 0.1, Java3D cenets box. */
                final float boxTrueWidth = boxWidth - boxPadding*2.0f;
                
                float boxX = x*boxWidth + boxWidth / 2.0f;
                heating3D.setTranslation(new Vector3d(
                        boxX,
                        y*boxWidth + boxWidth / 2.0f, 
                        -0.05));
                heatingTransform.setTransform(heating3D);
                
                Box heataingSurface = new Box(boxTrueWidth, boxTrueWidth, 0.1f, heatingAppearance);
                heataingSurface.setBoundsAutoCompute(true);
                heataingSurface.setAppearance(heatingAppearance);
                
                heatingTransform.addChild(heataingSurface);
                heatingSurfaceGrp.addChild(heatingTransform);
            }
        }
        
        heatingSurfaceGrp.compile();
        
        return heatingSurfaceGrp;
    }
    
    protected Group createObject3D(InputStream is) {
        TransformGroup transform = new TransformGroup();
        transform.setCapability(Group.ALLOW_CHILDREN_READ);
        transform.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); 
        transform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        transform.setCapability(BranchGroup.ALLOW_DETACH);
        
        STLParser parser = STLParserFactory.getInstance().createParser();
        
        Appearance appearance = new Appearance();
        //appearance.setColoringAttributes(new ColoringAttributes(new Color3f(1.0f, 0.0f, 0.0f), ColoringAttributes.NICEST));
        
        Material m = new Material();
        m.setDiffuseColor(new Color3f(0.9f, 0.0f, 0.0f));
        m.setAmbientColor(new Color3f(0.7f, 0.0f, 0.0f));
        m.setSpecularColor(new Color3f(0.5f, 0.0f, 0.0f));
        m.setEmissiveColor(new Color3f(0.4f, 0.0f, 0.0f));
        m.setShininess(80.0f);
        m.setLightingEnable(true);
        appearance.setMaterial(m);
        
        StlMimeVisualEditorParserHandler handler = new StlMimeVisualEditorParserHandler(appearance);
        parser.parse(is, handler);
        
        Transform3D transMatrix = new Transform3D();
        
        float scaleX = 0.1f, scaleY = 0.1f, scaleZ = 0.1f;
        transMatrix.setScale(new Vector3d(scaleX, scaleY, scaleZ));
        transMatrix.setTranslation(new Vector3f(
                -handler.getMinX().floatValue()*scaleX,
                -handler.getMinY().floatValue()*scaleY,
                -handler.getMinZ().floatValue()*scaleZ));
        transform.setTransform(transMatrix);
        
        transform.setBoundsAutoCompute(true);
        Shape3D object3D = handler.getShape();
        object3D.setBounds(new BoundingBox(
                new Point3d(handler.getMinX().floatValue(), handler.getMinY().floatValue(), handler.getMinZ().floatValue()), 
                new Point3d(handler.getMaxX().floatValue(), handler.getMaxY().floatValue(), handler.getMaxZ().floatValue())));
        
        transform.addChild(handler.getShape());
        BranchGroup group = new BranchGroup();
        group.setBoundsAutoCompute(true);
        group.addChild(transform);
        return group;
    }
    
    @Override
    public String getName() {
        
        return "StlMimeVisualElement";
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        setLayout(new java.awt.BorderLayout());
    }// </editor-fold>//GEN-END:initComponents

    // Variables declaration - do not modify//GEN-BEGIN:variables
    // End of variables declaration//GEN-END:variables
    @Override
    public JComponent getVisualRepresentation() {
        return this;
    }

    @Override
    public JComponent getToolbarRepresentation() {
        return toolbar;
    }

    @Override
    public Action[] getActions() {
        return new Action[0];
    }

    @Override
    public Lookup getLookup() {
        return obj.getLookup();
    }

    @Override
    public void componentOpened() {
    }

    @Override
    public void componentClosed() {
        remove(canvas3d);
        
        
        this.mainGroup.detach();
        this.mainGroup.removeAllChildren();
        
        this.objects.detach();
        //this.objects.removeAllChildren();
        
        canvas3d.removeMouseWheelListener(mouseAdapter);
        
        this.canvas3d = null;
        this.mainGroup = null;
        this.lights = null;
        this.objects = null;
        this.mouseAdapter = null;
        
        /* Do not remove this line... if you want save your memory. */
        this.su.cleanup();
    }

    @Override
    public void componentShowing() {
    }

    @Override
    public void componentHidden() {
    }

    @Override
    public void componentActivated() {
    }

    @Override
    public void componentDeactivated() {
    }

    @Override
    public UndoRedo getUndoRedo() {
        return UndoRedo.NONE;
    }

    @Override
    public void setMultiViewCallback(MultiViewElementCallback callback) {
        this.callback = callback;
    }

    @Override
    public CloseOperationState canCloseElement() {
        return CloseOperationState.STATE_OK;
    }
}
